﻿#if GX_WEBSOCKET
using System.Collections;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System;
using UnityEngine;

namespace GX.Net
{
	/// <summary>
	/// 基于`WebSocket`的`json`通信
	/// </summary>
	public partial class WebSocketJson : IDisposable
	{
		private string url;
		private WebSocket4Net.WebSocket socket;
		public MessageDispatcher<object> Dispatcher { get; private set; }

		private List<string> sendCache = new List<string>();
		private readonly object sendCacheLock = new object();

		private List<string> received = new List<string>();
		private readonly object receivedLock = new object();
		private Coroutine m_dispatchCoroutine;

		public WebSocketJson(string url)
		{
			this.url = url;
			this.socket = new WebSocket4Net.WebSocket(url);
			this.Dispatcher = new MessageDispatcher<object>();

			Debug.Log("[WS OPENING] " + url);

			this.socket.Opened += (s, e) =>
			{
				Debug.Log("[WS OPEN] " + url);
				lock (sendCacheLock)
				{
					if (this.sendCache != null)
					{
						foreach (var str in this.sendCache)
						{
							this.socket.Send(str);
							Debug.Log("[WS SEND CACHE] " + str);
						}
						this.sendCache = null;
					}
				}
			};

			this.socket.MessageReceived += (s, e) =>
			{
				Debug.Log("[WS RECV] " + e.Message);
				lock(receivedLock)
				{
					received.Add(e.Message);
				}
			};

			this.socket.Error += (s, e) =>
			{
				Debug.Log("[WS ERROR] " + url);
			};

			this.socket.Closed += (s, e) =>
			{
				Debug.Log("[WS CLOSE] " + url);
				this.socket = null;
			};

			this.m_dispatchCoroutine = Singleton.Instance.StartCoroutine(this.Dispatch());
			this.socket.Open();
		}
		
		private IEnumerator Dispatch()
		{
			while (Application.isPlaying)
			{
				yield return null;
				List<string> messages = null;
				lock(receivedLock)
				{
					if(received.Count == 0)
						continue;
					messages = received;
					received = new List<string>();
				}

				foreach(var json in messages)
				{
					var msg = Deserialize(json);
					if(msg == null)
						continue;
					IEnumerator coroutine;
					if (Dispatcher.Dispatch(msg, out coroutine) == false)
						Debug.LogWarning(string.Format("未处理的消息: {0}\n{1}", msg.GetType(), json));
					if (coroutine != null)
					{
						while (coroutine.MoveNext())
							yield return coroutine.Current;
					}
				}

			}
		}

		public void Send(object message, bool loopback)
		{
			if (message == null)
				return;
			var str = Serialize(message);
			if (string.IsNullOrEmpty(str))
				return;

			if (loopback == true)
			{
				lock (receivedLock)
				{
					received.Add(str);
				}
				return;
			}


			if (this.sendCache != null)
			{
				lock (sendCacheLock)
				{
					if (this.sendCache != null)
					{
						this.sendCache.Add(str);
						Debug.Log("[WS CACHE] " + str);
						return;
					}
				}
			}

			if (this.socket == null)
			{
				Debug.LogWarning("[WS SEND ERROR] can't send message when websocket closed: " + str);
				return;
			}

			this.socket.Send(str);
			Debug.Log("[WS SEND] " + str);
		}

		public void Close()
		{
			if (this.socket != null)
			{
				Debug.Log("[WS CLOSING] " + this.url);
				this.sendCache = null;
				this.socket.Close();
				this.socket = null;
			}
		}

		#region IDisposable 成员

		public void Dispose()
		{
			Close();
		}

		#endregion

		#region Serialization
		private const string GatewayWrapperName = "Pmd.UserJsMessageForwardUserPmd_CS";

		private static string Serialize(object message)
		{
			var messageType = message.GetType().FullName;
			// 所有不在`Pmd.`下面的消息需要采用`UserJsMessageForwardUserPmd_CS`包装，以方便网关服务器直接转发
			if (messageType.Substring(0, 4) != "Pmd.")
			{
				return Json.Serialize(new Dictionary<string, object>()
				{
					{"cmd_name", GatewayWrapperName},
					{"msg", Json.Serialize(new Dictionary<string, object>()
						{
							{"do", messageType},
							{"data", message},
						})},
				});
			}
			else
			{
				var str = Json.Serialize(message);
				if (str.StartsWith("{") == false)
				{
					Debug.LogError("[WS SEND ERROR] unsupported message format: " + str);
					return string.Empty;
				}
				return str.Insert(1, string.Format("\"cmd_name\":\"{0}\"", messageType));
			}
		}

		private static object Deserialize(string json)
		{
			try
			{
				var dic = Json.Deserialize<Dictionary<string, object>>(json);
				if (HttpRecvError(dic) != null)
				{
					Debug.LogError("[WS RUN ERROR] " + HttpRecvError(dic) + "\n" + json);
					return null;
				}
				var cmd_name = (string)dic["cmd_name"];
				// 采用 UserJsMessageForwardUserPmd_CS 包装的下行消息
				if (cmd_name == GatewayWrapperName)
				{
					var msg = Json.Deserialize<Dictionary<string, object>>((string)dic["msg"]);
					if (HttpRecvError(dic) != null)
					{
						Debug.LogError("[WS RUN ERROR] " + HttpRecvError(dic) + "\n" + json);
						return null;
					}
					var type = (string)msg["do"];
					var data = (string)msg["data"];
					return Json.Deserialize(type, data);
				}
				else
				{
					return Json.Deserialize(cmd_name, json);
				}
			}
			catch(Exception ex)
			{
				Debug.LogError("[WS PARSE ERROR] " + ex.Message + "\n" + json);
				return null;
			}
		}

		private static string HttpRecvError(Dictionary<string, object> dic)
		{
			if (dic == null)
				return "<null>";
			object errno;
			if (dic.TryGetValue("errno", out errno) == false)
				return null;
			var e = errno.ToString();
			if (e == "" || e == "0")
				return null;
			return e;
		}
		#endregion
	}
}
#endif